package com.ansion.basedb.database;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteTransactionListener;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.ansion.basedb.dao.model.BaseModel;
import com.ansion.basedb.database.sql.SQLRow;
import com.ansion.basedb.database.sql.SQLTable;
import com.ansion.basetool.preconditions.Preconditions;
import com.ansion.log.VLog;
import com.ansion.utils.BooleanCallback;

import java.util.ArrayList;
import java.util.List;

/**
 * author : sibei.chen
 * date   : 2020-01-03 14:01
 * desc   :
 * version: 1.0
 */
public interface IDatabaseManager {

    boolean isInited();

    /**
     * 加快数据库的打开速度, 所以需要预加载一下.
     */
    void preload();

    void setInited(boolean isInited);

    void initDataBase(Context context, String dbName, int version, List<Class<?>> models, List<SQLTable> models2);

    <T extends BaseModel> List<T> select(@Nullable String tableName, Class<T> claz, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);

    default <T extends BaseModel> List<T> rawQuery(Class<T> claz, String sql, String[] selectionArgs) {
        return null;
    }

    default Cursor rawQuery(String sql, String[] selectionArgs) {
        return null;
    }


    <T extends BaseModel> void insert(@Nullable String tableName, Class<T> claz, T t, DBOperateAsyncListener listener);

    <T extends BaseModel> void insert(@Nullable String tableName, Class<T> claz, List<T> models, DBOperateAsyncListener listener);

    <T extends BaseModel> void update(@Nullable String tableName, Class<T> claz, T t, DBOperateAsyncListener listener);

    <T extends BaseModel> void update(@Nullable String tableName, Class<T> claz, List<T> models, DBOperateAsyncListener listener);

    <T extends BaseModel> void replace(@Nullable String tableName, Class<T> claz, T t, DBOperateAsyncListener listener);

    <T extends BaseModel> void replace(@Nullable String tableName, Class<T> claz, List<T> models, DBOperateAsyncListener listener);

    <T extends BaseModel> void delete(@Nullable String tableName, Class<T> claz, String whereClause, String[] whereArgs, DBOperateDeleteListener listener);

    <T extends BaseModel> void delete(@Nullable String tableName, Class<T> claz, List<T> models);

    <T extends BaseModel> void updateColumn(@Nullable String tableName, Class<T> claz, T t, List<String> needUpdateColumn, DBOperateAsyncListener listener);

    <T extends BaseModel> void updateColumn(String tableName, Class<T> claz, List<T> list, List<String> needReplaceColumnName, DBOperateAsyncListener listener);

    <T extends BaseModel> void syncInsert(Class<T> claz, List<T> models);


    <T extends BaseModel> long syncInsert(Class<T> claz, T value, boolean bNeedNotify);

    <T extends BaseModel> void syncReplace(Class<T> claz, List<T> models);

    <T extends BaseModel> void syncDelete(Class<T> claz, String whereClause, String[] whereArgs);

    <T extends BaseModel> void syncDelete(String tableName, String whereClause, String[] whereArgs);

    <T extends BaseModel> String getUniqueColumn(Class<T> claz);

    void recycle();

    void closeDB();

    //------------------------------------------- Without tableName -------------------------------------------------
    default <T extends BaseModel> void insert(Class<T> claz, T t, DBOperateAsyncListener listener) {
        insert(null, claz, t, listener);
    }

    default <T extends BaseModel> void insert(Class<T> claz, List<T> models, DBOperateAsyncListener listener) {
        insert(null, claz, models, listener);
    }

    default <T extends BaseModel> List<T> select(Class<T> claz, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {
        return select(null, claz, columns, selection, selectionArgs, groupBy, having, orderBy, limit);
    }

    default <T extends BaseModel> void replace(Class<T> claz, T t, DBOperateAsyncListener listener) {
        replace(null, claz, t, listener);
    }

    default <T extends BaseModel> void update(Class<T> claz, T t, DBOperateAsyncListener listener) {
        update(null, claz, t, listener);
    }

    default <T extends BaseModel> void update(Class<T> claz, List<T> models, DBOperateAsyncListener listener) {
        update(null, claz, models, listener);
    }

    default <T extends BaseModel> void replace(Class<T> claz, List<T> models, DBOperateAsyncListener listener) {
        replace(null, claz, models, listener);
    }

    default <T extends BaseModel> void delete(Class<T> claz, String whereClause, String[] whereArgs, DBOperateDeleteListener listener) {
        delete(null, claz, whereClause, whereArgs, listener);
    }

    default <T extends BaseModel> void delete(Class<T> claz, List<T> models) {
        delete(null, claz, models);
    }

    default <T extends BaseModel> void updateColumn(Class<T> claz, T t, List<String> needUpdateColumn, DBOperateAsyncListener listener) {
        updateColumn(null, claz, t, needUpdateColumn, listener);
    }

    //------------------------------------------- Without callback -------------------------------------------------
    default <T extends BaseModel> void insert(Class<T> claz, T t) {
        insert(claz, t, null);
    }

    default <T extends BaseModel> void insert(@Nullable String tableName, Class<T> claz, T t) {
        insert(tableName, claz, t, null);
    }

    default <T extends BaseModel> void insert(Class<T> claz, List<T> models) {
        insert(claz, models, null);
    }

    default <T extends BaseModel> void insert(@Nullable String tableName, Class<T> claz, List<T> models) {
        insert(tableName, claz, models, null);
    }

    default <T extends BaseModel> void update(Class<T> claz, T t) {
        update(claz, t, null);
    }

    default <T extends BaseModel> void update(@Nullable String tableName, Class<T> claz, T t) {
        update(tableName, claz, t, null);
    }

    default <T extends BaseModel> void update(Class<T> claz, List<T> models) {
        update(claz, models, null);
    }

    default <T extends BaseModel> void update(@Nullable String tableName, Class<T> claz, List<T> models) {
        update(tableName, claz, models, null);
    }

    default <T extends BaseModel> void updateColumn(Class<T> claz, T t, List<String> needUpdateColumn) {
        updateColumn(claz, t, needUpdateColumn, null);
    }

    default <T extends BaseModel> void replace(Class<T> claz, T t) {
        replace(claz, t, null);
    }

    default <T extends BaseModel> void replace(@Nullable String tableName, Class<T> claz, T t) {
        replace(tableName, claz, t, null);
    }

    default <T extends BaseModel> void replace(Class<T> claz, List<T> models) {
        replace(claz, models, null);
    }

    default <T extends BaseModel> void replace(@Nullable String tableName, Class<T> claz, List<T> models) {
        replace(tableName, claz, models, null);
    }

    default <T extends BaseModel> void delete(Class<T> claz, String whereClause, String[] whereArgs) {
        delete(claz, whereClause, whereArgs, null);
    }

    default <T extends BaseModel> void delete(@Nullable String tableName, Class<T> claz, String whereClause, String[] whereArgs) {
        delete(tableName, claz, whereClause, whereArgs, null);
    }


    /***
     * this method is deprecated, use execSQL instead
     */
    @Deprecated
    default void execSql(String sql, Object[] args, BooleanCallback callback) {

    }

    void post(Runnable runnable);


    ISqlDataBase getDatabase();

    /***
     * insert or full update use this method, like room's saveEntity
     */
    default void replace(@NonNull String tableName, @NonNull List<SQLRow> list, BooleanCallback callback) {
        Preconditions.checkNotEmpty(list);
        execWithTransaction(() -> {
            for (SQLRow row : list) {
                long ret = getDatabase().replace(tableName, null, row.getContentValues());
                if (ret == -1) {
                    throw new RuntimeException(String.format("replace fail, table:%s", tableName));
                }
            }
        }, callback);
    }


    /***
     * sql query, support Multi-table query
     */
    default <T extends SqlDBInterface> @NonNull List<T> select(@NonNull ModelGenerator<T> generator, @NonNull String sql) {
        List<SQLRow> rows = SQLDBHelper.select(sql);
        List<T> result = new ArrayList<>();
        for (SQLRow row : rows) {
            T model = generator.newModel();
            model.readFromSQLRow(row);
            result.add(model);
        }
        return result;
    }


    default @NonNull List<SQLRow> select(@NonNull String sql) {
        return SQLDBHelper.select(sql);
    }


    /**
     * Return to the original SQL, directly write the where condition in the SQL statement, the logic is clearer
     */
    default void execSQL(@NonNull String sql) {
        execSQL(sql, null);
    }

    /***
     *
     * @param sql
     * @param callback
     */
    default void execSQL(@NonNull String sql, BooleanCallback callback) {
        post(() -> {
            try {
                getDatabase().execSQL(sql);
                if (callback != null)
                    callback.onResult(true);
            } catch (Exception e) {
                if (callback != null)
                    callback.onResult(false);
            }
        });
    }


    /**
     * update columns in a table, based on android's sqlite api
     */
    default void updateColumn(@NonNull SQLTable table, @NonNull List<SQLRow> sqlRows, BooleanCallback callback) {
        execWithTransaction(() -> {
            List<String> primaryKeys = table.getPrimaryKeyName();
            String whereClause = SqlQueryUtil.generateWhereClause(primaryKeys);
            int count = 0;
            for (SQLRow row : sqlRows) {
                String[] whereArgs = SqlQueryUtil.generateWhereArgs(primaryKeys, row);
                count += getDatabase().update(table.getTableName(), row.getContentValues(), whereClause, whereArgs);
            }
            //本身update就可能没有更新任何列，比如id不对，条件不对，它并不代表SQL执行报错，只有insert和replace才会出现SQL报错是需要区分successRows和failRows
            if (count == 0) {
                VLog.i("TAG_DB_SQLITE", "updateColumn fail");
            }
        }, callback);
    }

    /***
     * exec sql with transaction
     */
    default void execWithTransaction(@NonNull Runnable runnable, BooleanCallback callback) {
        post(() -> {
            getDatabase().beginTransactionWithListener(new SQLiteTransactionListener() {
                @Override
                public void onBegin() {
                    if (VLog.OPEN_LOG)
                        VLog.d("TAG_DB_SQLITE", "onBegin");
                }

                @Override
                public void onCommit() {
                    if (VLog.OPEN_LOG)
                        VLog.d("TAG_DB_SQLITE", "onCommit");
                }

                @Override
                public void onRollback() {
                    VLog.FileLog.e("TAG_DB_SQLITE", "onRollback");
                }
            });
            try {
                runnable.run();
                getDatabase().setTransactionSuccessful();
                if (callback != null)
                    callback.onResult(true);
            } catch (Exception e) {

                if (callback != null) {
                    callback.onResult(false);
                    callback.onFail(e);
                }

            } finally {
                try {
                    getDatabase().endTransaction();
                } catch (Exception e) {
                    VLog.FileLog.e(e, "TAG_DB_SQLITE", "endTransaction fail");
                }
            }
        });
    }

    default void syncExecWithTransaction(@NonNull Runnable runnable) {
        getDatabase().beginTransactionWithListener(new SQLiteTransactionListener() {
            @Override
            public void onBegin() {
                if (VLog.OPEN_LOG)
                    VLog.d("TAG_DB_SQLITE", "onBegin");
            }

            @Override
            public void onCommit() {
                if (VLog.OPEN_LOG)
                    VLog.d("TAG_DB_SQLITE", "onCommit");
            }

            @Override
            public void onRollback() {
                VLog.FileLog.e("TAG_DB_SQLITE", "onRollback");
            }
        });
        try {
            runnable.run();
            getDatabase().setTransactionSuccessful();
        } catch (Exception e) {
            VLog.FileLog.e(e, "TAG_DB_SQLITE", "syncExecWithTransaction fail");
        } finally {
            try {
                getDatabase().endTransaction();
            } catch (Exception e) {
                VLog.FileLog.e(e, "TAG_DB_SQLITE", "endTransaction fail");
            }
        }
    }


    boolean isEncryptDB();
}
